#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cstdbool>
#include<sys/wait.h>
#include<unistd.h>
#include<iostream>
#include<ctype.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define command_size 1024
#define format "[%s@%s %s]#"
//下面定义shell的全局变量 
char cwd[1024];
char cwdenv[1024];
using namespace std;
#define maxargc 128
char*  my_argv[maxargc];
#define maxenv 2000
char* my_env[maxenv];
int lastcode=0;
int argc=0;
int myenvs=0;
//重定向的内容
#define none_redir 0
#define input_redir 1
#define output_redir 2
#define append_redir 3
string filename;
int redir=none_redir;
void trimspace(char cmd[],int* end)
{
    while(isspace(cmd[*end]))
        (*end)++;

}
void redircheck(char* cmd)
{
    int start=0;
    int end=strlen(cmd)-1;
    while(end>start)
    {
        if(cmd[end]=='<')
        {
            cmd[end++]=0;//分割命令
            redir=input_redir;
            trimspace(cmd,&end);
            filename=cmd+end;
            break;
        }
        else if(cmd[end]=='>')
        {
            if(cmd[end-1]=='>')
            {
                cmd[end-1]=0;//分割命令
                cmd[end++]=0;
                redir=append_redir;
                trimspace(cmd,&end);
                filename=cmd+end;
                break;         
            }
            else 
            {
                redir=output_redir;
                cmd[end++]=0;
                trimspace(cmd,&end);
                filename=cmd+end;
                break;
            }
        }
        else end--;
    }
}
const char* getusername()
{
    const char* name=getenv("USER");
    return name==NULL?"None":name;
}
const char* gethostname()
{
    const char* hostname=getenv("HOSTNAME");
    return hostname==NULL?"None":hostname;
}
const char* getpath()
{
    const char* path=getcwd(cwd,sizeof(cwd));//获得当前进程的工作路径
    if(path)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd);
        putenv(cwdenv);
    }
    return path==NULL?"None":path;
}
const char* gethome()
{
    const char* home=getenv("HOME");
    return home==NULL?NULL:home;
}
void makecmdline(char cmd_prompt[],int size)
{
    snprintf(cmd_prompt,size,format,getusername(),gethostname(),getpath());   
}
void printcmdprompt()
{
    char prompt[command_size];
    makecmdline(prompt,sizeof(prompt));
    printf("%s ",prompt);
    fflush(stdout);
}
bool getcommandline(char* out,int size)
{    
    char* c=fgets(out,size,stdin);
    if(c==NULL) return false;
    out[strlen(out)-1]=0;
    if(sizeof(out)==0) return false;
    return true;
}
#define dsp " "
bool commandparse(char* commandline)
{
    argc=0;
    my_argv[argc++]=strtok(commandline,dsp); 
    while((bool)(my_argv[argc++]=strtok(NULL,dsp)));
    argc--;
    return argc>0?true:false;
}
int execute()
{
    pid_t id=fork();
    int status=0;
    if(id==0)//进程替换并不会影响文件的重定向，因为进程替换替换的的代码和数据，但是文件系统是操作系统层面的，所以进程替换并没有改变原来进程的文件描述符表，只有新建程序才会新建一个文件描述符表。进程替换只是替换了原来进程的数据和代码，新的进程继承了原进程的文件描述符表
       //误区：认为 exec 会像 fork() 一样复制文件描述符表。
//纠正：fork() 会复制文件描述符表，而 exec 直接复用原表。
//进程替换的本质
//不创建新进程：exec 仅替换当前进程的代码段、数据段等，进程 PID 不变，文件描述符表仍是原进程的（但原进程的代码已被覆盖）。
//未修改文件描述符表结构：文件描述符表本身（如大小、条目）未被修改，只是继承给新程序使用。   
    {
        int fd=-1;
        //子进程
        //用子进程检测重定向情况
        if(redir==input_redir)//输入重定向
        {
            fd=open(filename.c_str(),O_RDONLY);
            if(fd<0) exit(1);
            dup2(fd,0); //newfd为0，oldfd为fd，就是让0内的指针覆盖为fd中的指针
        }
        else if(redir==output_redir)
        {
            fd=open(filename.c_str(),O_CREAT|O_WRONLY,0666);
            if(fd<0) exit(2);
            dup2(fd,1);
        }
        else if(redir==append_redir)
        {
           fd=open(filename.c_str(),O_CREAT|O_APPEND|O_WRONLY,0666);
           if(fd<0) exit(3);
           dup2(fd,1);
        }
        execvp(my_argv[0],my_argv);    
        exit(1);
    }
    pid_t rid=waitpid(id,&status,0);
    if(rid>0)
    {
        lastcode=WEXITSTATUS(status);
        
    }
    return 0;
}
bool check()//查看是否是内建命令，并对内建命令进行相应的操作
{
    string cmd=my_argv[0];
    if(cmd=="cd") {
        if(argc==1)//返回家目录
        {
            if(gethome()==NULL) return true;
            else chdir(gethome());
        }
        else{
            string where=my_argv[1];
            chdir(where.c_str());
        }
        return true;
    }
    else if(cmd=="echo")
    {
        if(argc==2)
        {
            //echo "hello wworld"
            //echo $?
            //echo $PATH
            string opt=my_argv[1];
            if(opt=="$?") 
            {
                cout<<lastcode<<endl;
                lastcode=0;
            }
            else if(opt[0]=='$')//查找父进程的环境变量，因为我们今天并没有创建自己进程的环境变量表，所以我们还是使用从父进程继承的环境变量
            {
                string envname=opt.substr(1);
                if(getenv(envname.c_str())) cout<<getenv(envname.c_str())<<endl;
            }
            else 
            {
                cout<<opt<<endl;
            }
        }
        return true;
    }  
    else if(cmd=="export")
    {
        if(argc==2)
        {
            my_env[myenvs]=(char*)malloc(strlen(my_argv[1])+1);
            strcpy(my_env[myenvs++],my_argv[1]);//strcpy会拷贝字符串结尾的\0,但是strlen并不会将\0计算在内
            putenv(my_env[myenvs-1]);
        }     
        return true;
    }
    else return false;
}
void initenv()
{
    extern char** environ;
    memset(my_env,0,sizeof(my_env));
    myenvs=0;
    for(int i=0;environ[i];i++)
    {
        my_env[i]=(char*)malloc(strlen(environ[i])+1);
        strcpy(my_env[i],environ[i]);
        myenvs++;
    }
    my_env[myenvs++]=(char*)"test=haha";
    my_env[myenvs]=NULL;

    //导成环境变量
    for(int i=0;my_env[i];i++)
    {
        putenv(my_env[i]);//换将变量中如果没有这个环境变量名字就新增这个环境变量，否则就将其替换。
    }
}
int main()
{
    //shell启动的时候要从系统中获取环境变量
    //我们的环境变量的信息应该来原来父进程
    initenv();
    while(1)
    {
        redir=none_redir;
        filename.clear();
        //输出命令行提示符
        printcmdprompt();
        //获取用户输入的命令
        char commandline[command_size];
        if(!getcommandline(commandline,sizeof(commandline)))
            continue;
        //重定向分析
        redircheck(commandline);
        //命令行分析
        if(!commandparse(commandline)) continue;
        //检查是否是内建命令
        if(check()) continue;
        //执行命令
        execute();
    }
    return 0;
}
